/* Compilateur Scalpa : Grammaire YACC */

/* C code */
%{
#include "global.h"

// Définition des variables globales
int   is_error    = 0;
char* input_file  = "";
char* output_file = "output.s";
int   is_global   = 1;

symtable* table = NULL;
gencode*  data  = NULL;
gencode*  code  = NULL;
syntree*  tree  = NULL;
int labelno = 0;
%}

/* Mots-clés */
%token K_PROGRAM K_BEGIN K_END
%token K_FUNCTION K_REF K_ARRAY K_DP K_OF K_VAR K_BOOL K_UNIT K_INT
%token K_READ K_WRITE
%token K_WHILE K_DO
%token K_IF K_THEN K_ELSE
%token K_NOT 
%token K_NE K_GE K_LE K_OR K_AND K_XOR
%token K_AFF
%token K_RETURN
%token K_PARENTHESES

/* On redéfinit le type de yylval pour qu'il puisse prendre plusieurs types */
%union {
	int      type;
	int      integer;
	int      boolean;
	char*    string;
	syntree* node;
}

/* On définit les types correspondant à nos symboles non-terminaux */
%token <string>  STRING IDENT
%token <integer> INTEGER
%token <boolean> BOOL
%type  <node>    program vardecllist fundecllist instr
%type  <node>    fundecl varsdecl
%type  <node>    arglist arg
%type  <node>    rangelist
%type  <node>    sequence expr
%type  <node>    identlist typename
%type  <node>    exprlist   
%type  <type>    atomictype 
%type  <node>    arraytype
%type  <node>    lvalue cte

/* Priorités */
%right K_WHILE K_DO K_IF K_THEN K_ELSE
%left  K_OR K_XOR
%left  K_AND
%left  K_NE K_GE K_LE '=' '<' '>'
%left  '+' '-'
%right '%'
%left  '*' '/'
%left '^'
%left  K_MOINS K_NOT
%right K_PARENTHESES

/* Symbole de départ */
%start start

/* Grammaire */

%% 

start : program { tree = $1; }
      ;

program : K_PROGRAM IDENT vardecllist fundecllist instr { $$ = _op(O_PROGRAM, 3, $3, $4, $5); }
        ;

vardecllist : /* Rien */                { $$ = NULL; }
            | varsdecl                  { $$ = _op(O_VARSDECLLIST, 1, $1);     }
            | varsdecl ';' vardecllist  { $$ = _op(O_VARSDECLLIST, 2, $1, $3); }
            ;

varsdecl : K_VAR identlist ':' typename { $$ = _op(O_VAR, 2, $2, $4);
                                          table = table_add_identlist(table, $2, $4);
                                          if ($4->type == T_ARRAY)
                                          {
                                             table = table_mod_arraylist(table, $2, $4->array.debutTab);
                                          } }
         ;

identlist : IDENT               { $$ = _op(O_IDENTLIST, 1, _id_str($1)); }
          | IDENT ',' identlist { $$ = _op(O_IDENTLIST, 2, _id_str($1), $3); }	
          ;

typename : atomictype { $$ = _type($1); }
         | arraytype  { $$ = $1; }
         ;

atomictype : K_UNIT { $$ = T_UNIT; }
           | K_BOOL { $$ = T_BOOL; }
           | K_INT  { $$ = T_INT;  }
           ;

arraytype : K_ARRAY '[' rangelist ']' K_OF atomictype { $$ = _type_array($3, $6); }
          ;

rangelist : INTEGER K_DP INTEGER               { $$ = _range($1, $3);  }
          | INTEGER K_DP INTEGER ',' rangelist { $$ = NULL; /* TODO */ }
          ;

fundecllist : /* Rien */               { $$ = NULL; }
            | fundecl ';' fundecllist  { $$ = _op(O_FUNDECLLIST, 2, $1, $3); }
            ;

fundecl : K_FUNCTION IDENT '(' arglist ')' ':' atomictype vardecllist instr { $$ = _op(O_FUNCTION, 5, _id_str($2), $4, $7, $8, $9); }
        ;

arglist : /* Rien */       { $$ = NULL;                      }
        | arg              { $$ = _op(O_ARGLIST, 1, $1);     }
        | arg ',' arglist  { $$ = _op(O_ARGLIST, 2, $1, $3); }
        ;

arg : IDENT ':' typename        { $$ = NULL; /* TODO */ }
    | K_REF IDENT ':' typename  { $$ = NULL; /* TODO */ }
    ;

instr : K_IF expr K_THEN instr               { $$ = _op(O_IF, 2, $2, $4);     }
      | K_IF expr K_THEN instr K_ELSE instr  { $$ = _op(O_IF, 3, $2, $4, $6); }
      | K_WHILE expr K_DO instr              { $$ = _op(O_WHILE, 2, $2, $4);  }
      | lvalue K_AFF expr                    { $$ = _op(O_AFF, 2, $1, $3);    }
      | K_RETURN expr                        { $$ = _op(O_RETURN, 1, $2);     }
      | K_RETURN                             { $$ = _op(O_RETURN, 0);         }
      | IDENT '(' exprlist ')'               { $$ = _op(O_FUNCCALL, 2, _id_str($1), $3); }
      | IDENT '(' ')'                        { $$ = _op(O_FUNCCALL, 1, _id_str($1));     }
      | K_BEGIN sequence K_END               { $$ = _op(O_BEGIN, 1, $2);      }
      | K_BEGIN K_END                        { $$ = _op(O_BEGIN, 0);          }
      | K_READ lvalue                        { $$ = _op(O_READ, 1, $2);       }
      | K_WRITE lvalue                       { $$ = _op(O_WRITE, 1, $2);      }
      | K_WRITE cte                          { $$ = _op(O_WRITE, 1, $2);      }
      ;

sequence : instr ';' sequence { $$ = _op(O_SEQUENCE, 2, $1, $3); }
         | instr ';'          { $$ = _op(O_SEQUENCE, 1, $1);     }
         | instr              { $$ = _op(O_SEQUENCE, 1, $1);     }
         ;

lvalue : IDENT                  { $$ = _id($1); }
       | IDENT '[' exprlist ']' { $$ = _op(O_ARRAYACCESS, 2, _id_str($1), $3); }
       ;

exprlist : expr					{ $$ = _op(O_EXPRLIST, 1, $1); }
         | expr ',' exprlist	{ $$ = _op(O_EXPRLIST, 2, $1, $3); }
         ;

expr : cte                     { $$ = $1; }
     | '(' expr ')'            { $$ = $2; }
     | expr '+' expr           { $$ = _op(O_PLUS, 2, $1, $3);  }
     | expr '-' expr           { $$ = _op(O_MINUS, 2, $1, $3); }
     | expr '*' expr           { $$ = _op(O_TIMES, 2, $1, $3); }
     | expr '/' expr           { $$ = _op(O_DIV, 2, $1, $3);   }
     | expr '%' expr           { $$ = _op(O_MOD, 2, $1, $3);   }
     | expr '^' expr           { $$ = _op(O_POW, 2, $1, $3);   }
     | expr '<' expr           { $$ = _op(O_LT, 2, $1, $3);    }
     | expr K_LE expr          { $$ = _op(O_LE, 2, $1, $3);    }
     | expr '>' expr           { $$ = _op(O_GT, 2, $1, $3);    }
     | expr K_GE expr          { $$ = _op(O_GE, 2, $1, $3);    }
     | expr '=' expr           { $$ = _op(O_EQ, 2, $1, $3);    }
     | expr K_NE expr          { $$ = _op(O_NE, 2, $1, $3);    }
     | expr K_AND expr         { $$ = _op(O_AND, 2, $1, $3);   }
     | expr K_OR expr          { $$ = _op(O_OR, 2, $1, $3);    }
     | expr K_XOR expr         { $$ = _op(O_XOR, 2, $1, $3);   }
     | K_NOT expr              { $$ = _op(O_NOT, 1, $2);       }
     | '-' expr %prec K_MOINS  { $$ = _op(O_MINUS, 1, $2);     }
     | IDENT '(' exprlist ')'  { $$ = _op(O_FUNCCALL, 2, _id_str($1), $3);    }
     | IDENT '(' ')'           { $$ = _op(O_FUNCCALL, 1, _id_str($1));        }
     | IDENT '[' exprlist ']'  { $$ = _op(O_ARRAYACCESS, 2, _id_str($1), $3); }
     | IDENT                   { $$ = _id_str($1); }
     ;
     
cte : INTEGER { $$ = _int($1);  }
    | BOOL    { $$ = _bool($1); }
    | STRING  { $$ = _str($1);  }
    ;

%%

/* Code additionnel */

// Gestion des erreurs
int yyerror(char* s)
{
	is_error = 1;
	if (strncmp("syntax error", s, 12) == 0)
	{
		// Syntax error : on affiche le token responsable
		printf("%s:%d: %s : '%s'\n", input_file, yylineno, s, yytext);
	}
	else
	{
		// Erreur générée autrement, on n'affiche pas le token
		printf("%s:%d: %s\n", input_file, yylineno, s);
	}
	return -1;
}

int main(int argc, char** argv)
{
	// Vérification des arguments (fichier d'entrée[/sortie])
	switch (argc)
	{
		case 3 : // Entrée & sortie
			output_file = argv[2];
		case 2 : // Entrée
			input_file  = argv[1];
			break;
		default : // Mauvais argument : affichage de l'aide + exit
			printf("Usage: %s <input file> [ouput file]\n", argv[0]);
			exit(-1);
	}
	
	// Ouverture du fichier en entrée
	if ((yyin = fopen(input_file, "r+")) == NULL)
	{
		printf("Erreur : impossible d'ouvrir en lecture "
		       "le fichier '%s'\n", input_file);
		exit(-1);
	}
	
	printf("Compilation en cours (%s)\n", input_file);
	
	// Initialisation
	data = gencode_init();
	code = gencode_init();
	
	printf("\t- Analyse syntaxique\n");
	
	// Parsing
	yyparse();
	
	// Il y a eu une erreur, on s'arrête
	if (is_error)
	{
		exit(EXIT_FAILURE);
	}	
	
	// Arbre de syntaxe
	printf("\t- Génération de l'arbre de syntaxe\n");
	mips_generate(tree, data, code);
	
	// Écriture du code
	printf("\t- Génération du code (%s)\n", output_file);
	mips_write(output_file, data, code);
	
	// On a terminé, on désalloue toute la mémoire utilisée
	printf("\t- Libération des ressources\n");
	table_free(table);
	gencode_free(code);
	gencode_free(data);
	
	// THE END
	return 0;
}
